package com.wyvoid.core.tag;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
 * 显示一个月的日期的tag.默认显示当月.
 * @author cjp
 */
public class TGCalendarTag  extends TagSupport {
    private final static String DATE_PARAMETER_NAME = "tgdate";
    
    private static final long serialVersionUID = 3905528206810115095L;
    
    protected String requestURI = null;

    protected String linkURI = null;
    
    protected String name;
    
    protected String title;
    
    protected String styleId = "tgcalendar";
    
    protected boolean linkAll = false;

    protected boolean linkDay = false;

    protected boolean showToday = false;
    
    /**
     * @param uri the request URI to use in next/prev links.
     *
     */
    public void setRequestURI(String uri) {
        this.requestURI = uri;   
    }

    /**
     * @param uri the request URI to use if linkAll is set to 'true'.
     *
     */
    public void setLinkURI(String uri) {
        this.linkURI = uri;       
    }
    
    /**
     * @param name name of the request scope attribute that holds the
     * java.util.List of CalendarEvents to add to calendar view.
     *
     */
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * @param id name of the css style id for the calendar container.
     *
     */
    public void setStyleId(String id) {
        this.styleId = id;
    }
    
    /**
     * @param title title to display on the calendar.
     *
     */
    public void setTitle(String title) {
        this.title = title;
    }  
    
    /**
     * @param b if true, a link is generated for every day.
     *
     */
    public void setLinkAll(String b) {
        this.linkAll = Boolean.parseBoolean(b);
    }

    /**
     * @param linkDay if true, a link is generated for every day.
     */
    public void setLinkDay(String linkDay) {
        this.linkDay = Boolean.parseBoolean(linkDay);
    }

    /**
     * @param b if true, the actual day is generated with a special
     * css style id.
     */
    public void setShowToday(String b) {
        this.showToday = Boolean.parseBoolean(b);
    }  


    /**
     * Process the start of this tag.
     *
     * @return int
     *
     * @exception JspException if a JSP exception has occurred
     *
     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
     */
    public int doStartTag() throws JspException {
        Locale locale = pageContext.getRequest().getLocale();
        Calendar cal = Calendar.getInstance(locale);
        Calendar date = Calendar.getInstance(locale);      
        List events = null;
        DateFormat df;
        StringBuffer sb = new StringBuffer();
        
        // set requestURI
        HttpServletRequest req = ((HttpServletRequest) pageContext.getRequest());
        if (!requestURI.startsWith("http://")) {
            if (!requestURI.startsWith(req.getContextPath())) {
                requestURI = req.getContextPath() + requestURI;
            }
        }
        
        // set linkURI
        if (linkURI != null){
            if (!linkURI.startsWith("http://")) {
                if (!linkURI.startsWith(req.getContextPath())) {
                    linkURI = req.getContextPath() + linkURI;
                }
            }
        }
        else{
            linkURI = requestURI; 
        }
        
        // set date if any
        try{
            String dateParameter = pageContext.getRequest().getParameter(DATE_PARAMETER_NAME);
            date.setTimeInMillis(Long.parseLong(dateParameter));
        }
        catch(Exception e){}
        
        // set events if any
        try{
            events = (List)pageContext.getRequest().getAttribute(name);
        }
        catch(Exception e){}
        
        // prepare output
        sb.append("<div id=\"").append(styleId).append("\">\n");
        
        if (title != null && title.length() > 0){
            sb.append(title);
        }
        
        sb.append("<table class=\"tgcalTable\" cellspacing=\"3\" cellpadding=\"2\">\n");
        
        // line : header
        df = new SimpleDateFormat("MMMMM yyyy", locale);
        cal.setTime(date.getTime());
        cal.getTimeInMillis();// to force cal to update
        sb.append("<tr class=\"calendarHeader\">");
        sb.append("<th>");
        cal.add(Calendar.MONTH, -1);
        sb.append("<a class=\"tgcalPrevious\" href=\"").append(requestURI).append("&tgperiod=month&tgdate=").append(cal.getTimeInMillis()).append("\">");
        sb.append("&nbsp;&nbsp;</a>");
        sb.append("</th>\n");
        sb.append("<th colspan=\"5\" class=\"tgcalTitle\">");
        sb.append(df.format(date.getTime()));
        sb.append("</th>\n");
        sb.append("<th style=\"text-align: right\">");
        cal.add(Calendar.MONTH, 2);
        sb.append("<a class=\"tgcalNext\" href=\"").append(requestURI).append("&tgperiod=month&tgdate=").append(cal.getTimeInMillis()).append("\">");
        sb.append("&nbsp;&nbsp;</a>");
        sb.append("</th>\n");
        sb.append("</tr>\n");

        // line : days of week
        sb.append("<tr class=\"tgcalWeekDays\">\n");
        cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        
        for (int i = 1; i <= 7; i++){
            sb.append("<td width=\"14%\" class=\"tgcalWeekDay\">");
            sb.append(i);
            sb.append("</td>\n");
            cal.add(Calendar.DATE, 1);
        }
        sb.append("</tr>\n");
        
        // lines : content
        TGCalendarDay[] days = preprocess(events, date);
        sb.append("<tr>\n");
        for (int i = 0; i < days.length && days[i] != null; i++){
            // new line every 7
            if (i % 7 == 0 && i != 0){
                sb.append("</tr>\n<tr>");
            }
            sb.append(days[i].toString());
        }
        sb.append("</tr>\n");
        sb.append("<tr><td colspan=7 align=center>" + "<a href=\"");
        sb.append(requestURI);
        sb.append("&tgperiod=day&tgdate=");
        sb.append(System.currentTimeMillis());
        sb.append("\">当天</a>&nbsp;&nbsp;&nbsp;&nbsp;\n" + "<a href=\"");
        sb.append(requestURI);
        sb.append("&tgperiod=month&tgdate=");
        sb.append(System.currentTimeMillis());
        sb.append("\">当月</a>" + "</td></tr>");
        sb.append("</table>\n");
        sb.append("</div>\n");
        
        // writes output
        try{
            pageContext.getOut().write(sb.toString());
        } 
        catch (IOException io) {
            throw new JspException(io);
        }

        return super.doStartTag();
    }

    /**
     * Release aquired resources to enable tag reusage.
     *
     * @see javax.servlet.jsp.tagext.Tag#release()
     */
    public void release() {
        super.release();
    }
    
    protected TGCalendarDay[] preprocess(List events, Calendar date){
        TGCalendarDay[] days = new TGCalendarDay[42];
        TGCalendarDay day ;
        TGCalendarEvent evt;
        Calendar tmpCal = Calendar.getInstance();
        int tmpInt = 0;
        int offset = 0;
        int month, year;
        
        tmpCal.setTime(date.getTime());
        month = tmpCal.get(Calendar.MONTH);
        year = tmpCal.get(Calendar.YEAR);
        
        // uggly way to reach the last monday of former month
        tmpCal.getTimeInMillis();// to force cal to update
        tmpCal.set(Calendar.DATE, 1);
        tmpCal.getTimeInMillis();// to force cal to update
        tmpCal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        tmpCal.getTimeInMillis();// to force cal to update
        
        // step 1 : days before
        while (tmpCal.get(Calendar.DATE) != 1) {
            day = new TGCalendarDay();
            day.setStyleClass("tgcalDayOut");
            day.addLine(tmpCal.get(Calendar.DATE) + ""); 
            days[tmpInt++] = day;
            offset++;
            tmpCal.add(Calendar.DATE, 1);
        }
        
        // step 2 : days of the month
        while (tmpCal.get(Calendar.MONTH) == date.get(Calendar.MONTH)) {
            day = new TGCalendarDay();
            day.setStyleClass("tgcalDayIn");
            day.setDay(tmpCal.get(Calendar.DATE));
            if (linkAll){
                // link for every day
                day.addLine("<a href=\"" + linkURI + "&tgdate=" + tmpCal.getTimeInMillis()
                        + "\" class=\"tgcalDayLink\">" 
                        + tmpCal.get(Calendar.DATE) + "</a>");
            }
            else{
                // just the date
                day.addLine(tmpCal.get(Calendar.DATE) + "");   
            }
            if (tmpCal.get(Calendar.DATE) == date.get(Calendar.DATE)){
                // selected date
                day.setStyleId("tgcalSelected");
            }
            if (tmpCal.get(Calendar.DATE) == Calendar.getInstance().get(Calendar.DATE)){
                // today
                if (showToday){
                    day.setStyleId("tgcalToday");
                }
            }
            days[tmpInt++] = day;
            tmpCal.add(Calendar.DATE, 1);
        }
        
        // step 3 : days after the month
        while (tmpInt % 7 != 0) {
            day = new TGCalendarDay();
            day.addLine(tmpCal.get(Calendar.DATE) + ""); 
            day.setStyleClass("tgcalDayOut");
            days[tmpInt++] = day;
            tmpCal.add(Calendar.DATE, 1);
        }
        
        if (events != null && events.size() > 0){
            // step 4 : insert events info
            for (Object event : events) {
                evt = (TGCalendarEvent) event;
                tmpCal.setTime(evt.getStart());
                tmpInt = 1;
                if (evt.getStart().getTime() == evt.getEnd().getTime()) {
                    if (tmpCal.get(Calendar.MONTH) == month && tmpCal.get(Calendar.YEAR) == year) {
                        day = days[tmpCal.get(Calendar.DATE) + offset - 1];
                        day.setStyleClass("tgcalEvent");
                        if (linkDay) {
                            day.setLines("<a href =\"" + evt.getLink() + "&tgperiod=day&tgdate=" + tmpCal.getTimeInMillis() + "\" class=\"tgcalDayLink\">"
                                    + day.getDay() + "</a>");
                        } else {
                            if (evt.getIcon() != null) {
                                day.addLine("<img class=\"tgCalIcon\" src=\"" + evt.getIcon() + "\"/>"
                                        + "<a href =\"" + evt.getLink() + "\" class=\"tgcalDayLink\">"
                                        + evt.getTitle() + "</a>");
                            } else {
                                day.addLine("<a href =\"" + evt.getLink() + "\" class=\"tgcalDayLink\">"
                                        + evt.getTitle() + "</a>");
                            }
                        }
                    }
                    tmpCal.add(Calendar.DATE, 1);
                } else {
                    while (!tmpCal.getTime().after(evt.getEnd())) {
                        if (tmpCal.get(Calendar.MONTH) == month && tmpCal.get(Calendar.YEAR) == year) {
                            day = days[tmpCal.get(Calendar.DATE) + offset - 1];
                            day.setStyleClass("tgcalEvent");
                            if (linkDay) {
                                day.setLines("<a href =\"" + evt.getLink() + "&tgperiod=day&&tgdate=" + tmpCal.getTimeInMillis() + "\" class=\"tgcalDayLink\">"
                                        + day.getDay() + "</a>");
                            } else {
                                if (evt.getIcon() != null) {
                                    day.addLine("<img class=\"tgCalIcon\" src=\"" + evt.getIcon() + "\"/>"
                                            + "<a href =\"" + evt.getLink() + "\" class=\"tgcalDayLink\">"
                                            + evt.getTitle() + " (" + tmpInt++ + ")</a>");
                                } else {
                                    day.addLine("<a href =\"" + evt.getLink() + "\" class=\"tgcalDayLink\">"
                                            + evt.getTitle() + " (" + tmpInt++ + ")</a>");
                                }
                            }
                        }
                        tmpCal.add(Calendar.DATE, 1);
                    }
                }
            }
        }
        
        return days;
    }

}
